package org.simpatia;

import flexjson.JSONDeserializer;
import flexjson.JSONSerializer;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class SimpatiaContainer {

    private Map<String, Module> services;
    private boolean isStarted = false;
    private Context context;

    public Map<String, Module> getServices() {
        return services;
    }

    public void start(String agentArgs, Instrumentation instrumentation, String configLocation) {
        try {
            start(agentArgs, instrumentation, new FileReader(configLocation));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public synchronized void start(String agentArgs, Instrumentation instrumentation, Reader jsonConfiguration) {

        if(isStarted) {
            throw new RuntimeException("Container is already started");
        }

        isStarted = true;

        Configuration configuration = parseConfiguration(jsonConfiguration);

        context = new Context();
        context.setAgentArgs(agentArgs);
        context.setInstrumentation(instrumentation);

        services = new TreeMap<String, Module>();
        for(ServiceConfiguration conf : configuration.getServices()) {
            createService(conf);
        }

        SimpatiaServiceImpl simpatiaService = new SimpatiaServiceImpl();
        simpatiaService.setContainer(this);
        Module simpatia = new Module();
        simpatia.setInstance(simpatiaService);
        services.put("simpatia", simpatia);

        for(Module  service : services.values()) {
            for(String serviceName : services.keySet()) {
                service.getInstance().registerService(serviceName, services.get(serviceName));
            }
        }
    }

    private Configuration parseConfiguration(Reader jsonConfiguration) {
        JSONSerializer serializer = new JSONSerializer();
        JSONDeserializer deserializer = new JSONDeserializer();

        Map c;
        try {
            c = (Map)deserializer.deserialize(jsonConfiguration);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        String initialConfigurationKey = "initialConfiguration";
        for(Object service : (List)c.get("services")) {
            Object config = ((Map)service).get(initialConfigurationKey);
            JSONSerializer s = new JSONSerializer();
            String serializedConfig = s.deepSerialize(config);
            ((Map)service).put(initialConfigurationKey, serializedConfig);
        }

        String c2 = serializer.deepSerialize(c);
        Configuration configuration = new Configuration();
        deserializer.deserializeInto(c2, configuration);

        return configuration;
    }

    public synchronized void stop() {
        assertStarted();

        for(Module service : services.values()) {
            service.getInstance().destroy();
        }
        services = null;
        isStarted = false;
    }

    public synchronized void createService(ServiceConfiguration configuration) {
        assertStarted();

        Module module = new Module();
        module.setConfiguration(configuration);

        try {
            Service service = (Service) (Class.forName(configuration.getClassName()).newInstance());
            module.setInstance(service);
            services.put(configuration.getName(), module);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        activateService(module.getInstance(), configuration);

        for(Module  service : services.values()) {
            service.getInstance().registerService(configuration.getName(), module);
        }
    }

    private void activateService(Service service, ServiceConfiguration configuration) {
        JSONDeserializer deserializer = new JSONDeserializer();

        try {
            Class configurationClass = null;
            for(Method method : service.getClass().getDeclaredMethods()) {
                if(method.getName().equals("activate")) {
                    Class[] parameters = method.getParameterTypes();
                    if(parameters.length == 2
                            && method.getParameterTypes()[1] != Object.class
                            && !method.getParameterTypes()[1].isPrimitive() ) {
                        configurationClass = method.getParameterTypes()[1];
                    }
                }
            }

            Object initialConfiguration = null;
            if(configurationClass != null) {
                initialConfiguration = configurationClass.newInstance();
                deserializer.deserializeInto(configuration.getInitialConfiguration(), initialConfiguration);
            }

            service.activate(configuration.getName(), initialConfiguration, context);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public synchronized void destroyService(String serviceName) {
        assertStarted();

        Module module = services.get(serviceName);
        if(module != null) {
            for(Module s : services.values()) {
                s.getInstance().unregisterService(serviceName);
            }
            module.getInstance().destroy();
            services.remove(serviceName);
        } else {
            throw new RuntimeException("No service [" + serviceName + "]");
        }
    }

    private void assertStarted() {
        if(!isStarted) {
            throw new RuntimeException("Container is not started");
        }
    }
}
